﻿// <copyright file="InputPort.cs" company="Joe Chung">
// Copyright © 2008 by Joe Chung.
// </copyright>
namespace NScheme
{
    using System;
    using System.IO;
    using System.Text;

    public sealed class InputPort : NScheme.SchemeUtils
    {
        public const string EOF = "#!EOF";

        public bool isPushedToken = false;

        public bool isPushedChar = false;

        public object pushedToken = null;

        public int pushedChar = -1;

        public TextReader inp;

        public StringBuilder buff = new StringBuilder();

        public InputPort(TextReader inp)
        {
            this.inp = inp;
        }

        public InputPort(Stream inp)
            : this(new StreamReader(inp))
        {
        }

        public static bool isEOF(object x)
        {
            return x as string == EOF;
        }

        public object close()
        {
            try
            {
                this.inp.Close();
                return TRUE;
            }
            catch (IOException ex)
            {
                return error("IOException: " + ex);
            }
        }

        public object readChar()
        {
            try
            {
                if (this.isPushedChar)
                {
                    this.isPushedChar = false;

                    if (pushedChar == -1)
                    {
                        return EOF;
                    }
                    else
                    {
                        return chr((char)pushedChar);
                    }
                }
                else
                {
                    int ch = inp.Read();
                    if (ch == -1)
                    {
                        return EOF;
                    }
                    else
                    {
                        return chr((char)ch);
                    }
                }
            }
            catch (IOException ex)
            {
                warn("On input, exception: " + ex);
                return EOF;
            }
        }

        public int peekCh()
        {
            try
            {
                return this.isPushedChar ? pushedChar : pushChar(inp.Read());
            }
            catch (IOException ex)
            {
                warn("On input, exception: " + ex);
                return -1;
            }
        }

        public object peekChar()
        {
            int p = peekCh();
            if (p == -1)
            {
                return EOF;
            }
            else
            {
                return chr((char)p);
            }
        }

        public object read()
        {
            try
            {
                object token = this.nextToken();

                if (token as string == "(")
                {
                    return this.readTail(false);
                }
                else if (token as string == ")")
                {
                    warn("Extra ) ignored.");
                    return this.read();
                }
                else if (token as string == ".")
                {
                    warn("Extra . ignored.");
                    return this.read();
                }
                else if (token as string == "'")
                {
                    return list("quote", this.read());
                }
                else if (token as string == "`")
                {
                    return list("quasiquote", this.read());
                }
                else if (token as string == ",")
                {
                    return list("unquote", this.read());
                }
                else if (token as string == ",@")
                {
                    return list("unquote-splicing", this.read());
                }
                else
                {
                    return token;
                }
            }
            catch (IOException ex)
            {
                warn("On input, exception:" + ex);
                return EOF;
            }
        }

        public object nextToken()
        {
            int ch;

            // See if we should re-use a pushed char or token
            if (isPushedToken)
            {
                isPushedToken = false;
                return pushedToken;
            }
            else if (isPushedChar)
            {
                ch = popChar();
            }
            else
            {
                ch = inp.Read();
            }

            // Skip whitespace
            while (char.IsWhiteSpace((char)ch))
            {
                ch = inp.Read();
            }

            // See what kind of non-whitespace character we got
            switch (ch)
            {
                case -1:
                    return EOF;

                case '(':
                    return "(";

                case ')':
                    return ")";

                case '\'':
                    return "'";

                case '`':
                    return "`";

                case ',':
                    ch = inp.Read();
                    if (ch == '@')
                    {
                        return ",@";
                    }
                    else
                    {
                        pushChar(ch);
                        return ",";
                    }

                case ';':
                    while (ch != -1 && ch != '\n' && ch != '\r')
                    {
                        ch = this.inp.Read();
                    }

                    return this.nextToken();

                case '"':
                    buff.Length = 0;
                    while ((ch = inp.Read()) != '"' & ch != -1)
                    {
                        buff.Append((char)((ch == '\\') ? this.inp.Read() : ch));
                    }

                    if (ch == -1)
                    {
                        warn("EOF inside of a string.");
                    }

                    return buff.ToString().ToCharArray();

                case '#':
                    switch (ch = inp.Read())
                    {
                        case 't':
                        case 'T':
                            return TRUE;

                        case 'f':
                        case 'F':
                            return FALSE;

                        case '(':
                            pushChar('(');
                            return listToVector(read());

                        case '\\':
                            ch = inp.Read();
                            if (ch == 's' || ch == 'S' || ch == 'n' || ch == 'N')
                            {
                                pushChar(ch);
                                object token = nextToken();
                                if (token as string == "space")
                                {
                                    return chr(' ');
                                }
                                else if (token as string == "newline")
                                {
                                    return chr('\n');
                                }
                                else
                                {
                                    isPushedToken = true;
                                    pushedToken = token;
                                    return chr((char)ch);
                                }
                            }
                            else
                            {
                                return chr((char)ch);
                            }

                        case 'e':
                        case 'i':
                        case 'd':
                            return this.nextToken();

                        case 'b':
                        case 'o':
                        case 'x':
                            warn("#" + ((char)ch) + " not implemented, ignored.");
                            return nextToken();

                        default:
                            warn("#" + ((char)ch) + " not implemented, ignored.");
                            return nextToken();
                    }

                default:
                    buff.Length = 0;
                    int c = ch;
                    do
                    {
                        buff.Append((char)ch);
                        ch = inp.Read();
                    }
                    while (!char.IsWhiteSpace((char)ch) && ch != -1 && ch != '(' && ch != ')' & ch != '\'' && ch != ';' && ch != '"' && ch != ',' && ch != '`');

                    this.pushChar(ch);
                    if (c == '.' || c == '+' || c == '-' || (c >= '0' && c <= '9'))
                    {
                        double value;
                        if (double.TryParse(this.buff.ToString(), out value))
                        {
                            return value;
                        }
                    }

                    return string.Intern(this.buff.ToString().ToLower());
            }
        }

        public int pushChar(int ch)
        {
            this.isPushedChar = true;
            this.pushedChar = ch;
            return ch;
        }

        public int popChar()
        {
            this.isPushedChar = false;
            return this.pushedChar;
        }

        public object readTail(bool dotOK)
        {
            object token = nextToken();
            if (token as string == EOF)
            {
                return error("EOF during read.");
            }
            else if (token as string == ")")
            {
                return null;
            }
            else if (token as string == ".")
            {
                object result = read();
                token = nextToken();
                if (token as string != ")")
                {
                    warn("Where's the ')'? Got " + token + " after .");
                }

                return result;
            }
            else
            {
                isPushedToken = true;
                pushedToken = token;
                return cons(read(), readTail(true));
            }
        }
    }
}